The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. Arquillian Drone brings the power of WebDriver into the Arquillian framework. WebDriver provides a language how to communicate with a browser, like filling the forms, navigating on the pages and validating their content.
Why should I use Arquillian Drone instead of plain WebDriver?
There are many reasons why you want to do that, the most important being:
-
Life cycle management of the browser
-
Interaction with deployments and containers provided by Arquillian
-
Simple usage of multiple browsers in a single test
-
Configuration kept on a single place, outside of the Java code
-
Fully compatible with the IDE
-
Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2
-
Integration with mobile based browsers testing (Arquillian Droidium)
-
Integration of JavaScript test suite execution (QUnit)
-
Compatible with WebDriver (Selenium 2) and Selenium Grids
The following example illustrates how Arquillian Drone can be used with WebDriver:
@RunWith(Arquillian.class)
public class WebDriverTest {
static final String USERNAME = "demo";
static final String PASSWORD = "demo";
@ArquillianResource
URL contextPath;
@Drone
WebDriver driver;
/**
* Creates a testing WAR of using ShrinkWrap
*
* @return WebArchive to be tested
*/
@Deployment(testable = false)
public static WebArchive createDeployment() {
return Deployments.createDeployment();
}
@Test
@InSequence(1)
public void login() {
LoginPage page = new LoginPage(driver, contextPath);
page.login(USERNAME, PASSWORD);
}
@Test
@InSequence(2)
public void logout() {
LoginPage page = new LoginPage(driver, contextPath);
page.logout();
}
}
You can see that the Arquillian Drone test looks like an Arquillian test. There is @RunWith(Arquillian.class) runner, a @Deployment method and a few @Test methods. The only new elements are @ArquillianResource, which is here used to inject the URL of the deployed application and @Drone, which injects a WebDriver browser, managed for you as described in extensions.drone.lifecycle.
Even when using JUnit, Arquillian allows you to force method execution order via the @InSequence annotation. Arquillian Drone is obviously compatible with TestNG as well.
The testable=false argument for deployment forces Arquillian to run in client mode, that is not inside of the server where the application is deployed.
All Drone tests must run in client mode. If you need to combine tests running inside of the server as well as on the client using single deployments, mark the deployment as testable=true and force client execution via the @RunAsClient annotation on every client @Test method. More details are listed in Arquillian Documentation test run modes.
For the completeness of the code, here are the deployment methods as well as the LoginPage abstraction:
public class LoginPage {
private static final By LOGGED_IN = By.xpath("//li[contains(text(),'Welcome')]");
private static final By LOGGED_OUT = By.xpath("//li[contains(text(),'Goodbye')]");
private static final By USERNAME_FIELD = By.id("loginForm:username");
private static final By PASSWORD_FIELD = By.id("loginForm:password");
private static final By LOGIN_BUTTON = By.id("loginForm:login");
private static final By LOGOUT_BUTTON = By.id("loginForm:logout");
private final WebDriver driver;
private final URL contextPath;
public LoginPage(WebDriver driver, URL contextPath) {
this.driver = driver;
this.contextPath = contextPath;
}
public void login(String name, String password) {
driver.get(contextPath + "home.jsf");
driver.findElement(USERNAME_FIELD).sendKeys(USERNAME);
driver.findElement(PASSWORD_FIELD).sendKeys(PASSWORD);
driver.findElement(LOGIN_BUTTON).click();
Assert.isTrue("User is logged in.", driver.findElement(LOGGED_IN).isDisplayed());
}
public void logout() {
driver.findElement(LOGOUT_BUTTON).click();
Assert.isTrue("User is not logged in", driver.findElement(LOGGED_OUT).isDisplayed(), "User is logged out");
}
}
public class Deployments {
public static WebArchive createDeployment() {
WebArchive war = ShrinkWrap.create(WebArchive.class)
// add classes
.addClasses(Credentials.class, LoggedIn.class, Login.class, User.class, Users.class)
// add configuration
.addAsResource("META-INF/persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource(new File("src/test/webapp/WEB-INF/beans.xml"))
.addAsWebInfResource(new File("src/test/webapp/WEB-INF/faces-config.xml"))
// add pages
.addAsWebResource(new File("src/test/webapp/index.html"))
.addAsWebResource(new File("src/test/webapp/home.xhtml"))
.addAsWebResource(new File("src/test/webapp/template.xhtml"))
.addAsWebResource(new File("src/test/webapp/users.xhtml"))
.setWebXML(new File("src/test/webapp/WEB-INF/web.xml"));
return war;
}
}
Supported frameworks and their versions
The following frameworks are supported and tested with the latest version of Arquillian Drone. Drone type is the type you can inject via the @Drone annotation.
Framework name
|
Drone type
|
Tested version
|
WebDriver
|
ChromeDriver FirefoxDriver HtmlUnitDriver InternetExplorerDriver PhantomJSDriver OperaDriver RemoteDriver WebDriver
|
2.42.0
|
Arquillian Graphene
|
WebDriver
|
2.0.3.Final
|
It is not required to use Arquillian Drone with the exact version we certified. You can override versions via <dependencyManagement>, as explained in the Arquillian FAQ.
If you are in doubt what to use for a newly created project, Arquillian team recommends you to start with Graphene, which is based on WebDriver, however brings you a lot of AJAX goodies.
Maven setup example
Adding an Arquillian Drone dependency can be divided into two parts:
-
Adding a Bill of Materials (BOM) into the dependency section for both Arquillian and Arquillian Drone. This step ensures that Maven will fetch the correct version of all dependencies.
-
Adding a Dependency Chain dependency. This greatly simplifies the entry point as you only need to add a single dependency. All transitive dependencies, like the version of Selenium, will be fetched for you automatically.
The order in the <dependencyManagement> section matters; the first version defined takes precedence. By listing Arquillian BOM before Arquillian Drone BOM, you encore Drone to use latest Arquillian Core.
As for the first step, this is the same for all supported Drones:
<properties>
<version.org.jboss.arquillian>1.1.4.Final</version.org.jboss.arquillian>
<version.org.jboss.arquillian.drone>1.3.1.Final</version.org.jboss.arquillian.drone>
<version.org.jboss.arquillian.graphene>2.0.3.Final</version.org.jboss.arquillian.graphene>
</properties>
<dependencyManagement>
<dependencies>
<!-- Arquillian Core dependencies -->
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${version.org.jboss.arquillian}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Arquillian Drone dependencies and WebDriver/Selenium dependencies -->
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>${version.org.jboss.arquillian.drone}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The latter step differs based on what Drone you want to use. Include one of the following into the <dependencies> section:
To use Arquillian Graphene 2:
<dependency>
<groupId>org.jboss.arquillian.graphene</groupId>
<artifactId>graphene-webdriver</artifactId>
<version>${version.org.jboss.arquillian.graphene}</version>
<type>pom</type>
<scope>test</scope>
</dependency>
To use WebDriver, also known as Selenium 2:
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-webdriver-depchain</artifactId>
<version>${version.org.jboss.arquillian.drone}</version>
<type>pom</type>
<scope>test</scope>
</dependency>
WebDriver is a subset of Graphene. You can import Graphene and not to use any of the Graphene features from the start. However, it would be super easy to add them later on.
Life cycle of @Drone objects
Arquillian Drone does not allow you to control the life cycle of web testing framework objects, but it provides two different scenarios which should be sufficient for most usages required by developers. These are
-
Class based life cycle
-
Method based life cycle
For class based life cycle, configuration for the instance is created before a test class is run. This configuration is used to properly initialize an instance of the tool. The instance is injected into the field and holds until the last test in the test class is finished, then it is disposed. You can think of @BeforeClass and @AfterClass equivalents.
For method based life cycle, an instance is configured and created before Arquillian enters test method and it is disposed after method finishes. You can think of @Before and @After equivalents.
It is import to know that you can combine multiple instances in one tests and you can have them in different scopes. You can as well combine different framework types. Following example shows class based life cycle instance foo of type WebDriver combined with method based life cycle bar of type FirefoxDriver.
@RunWith(Arquillian.class)
public class EnrichedClass
{
@Drone WebDriver foo;
// this will always retrieve FirefoxDriver, no matter what you specify in arquillian.xml file
@Test
public void runThisTestAlwaysWithFirefoxDriver(@Drone FirefoxDriver bar) {
...
}
}
Keeping multiple Drone instances of the same field type
With Arquillian Drone, it is possible to keep more than one instance of a web test framework tool of the same type and determine which instance to use in a type safe way. Arquillian Drone uses the concept of a @Qualifier annotation which you may know from CDI. Drone defines its own @Qualifier meta-annotation which allows you to create your own annotations usable to qualify any @Drone injections. By default, if no @Qualifier annotation is present, Arquillian Drone implicitly uses the @Default qualifier. The following code defines a new qualifying annotation named Different.
Take care to not accidentally import the Qualifier annotation defined by CDI (javax.inject.Qualifier). Drone defines its own meta-annotation of the same name.
package org.jboss.arquillian.drone.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jboss.arquillian.drone.api.annotation.Qualifier;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Qualifier
public @interface Different {
}
Once you have defined a qualifier, you can use it in you tests, for example in following way, having two distinct class based life cycle instances of WebDriver.
@RunWith(Arquillian.class)
@RunAsClient
public class EnrichedClass {
@Drone WebDriver foo;
@Drone @Different WebDriver bar;
@Test
public void testWithBothFooAndBar() {
...
}
}
Configuring Drone instances
Drone instances are automatically configured from arquillian.xml descriptor file or System properties, which take precedence. You can eventually omit the configuration altogether, if you are happy with the default values. Obviously, configurations are compatible with @Qualifier annotations, so you can create a special configuration for a method based life cycle browser if you will.
Extension qualifier must match the value listed in configuration. Otherwise Drone won't pick up the configuration.
Default Drone configuration
Drone global configuration is applied for all supported frameworks at the same time. It uses drone extension qualifier.
<extension qualifier="drone">
<property name="instantiationTimeoutInSeconds">120</property>
</extension>
Property name
|
Default value
|
Description
|
instantiationTimeoutInSeconds
|
60
|
Default timeout in seconds to get instance of a browser. Set to 0 if you want to disable the timeout altogether
|
WebDriver configuration
WebDriver uses webdriver qualifier.
<extension qualifier="webdriver">
<property name="browser">firefox</property>
</extension>
Property name
|
Default value
|
Description
|
browser
|
htmlUnit
|
Determines which browser instance is created for WebDriver testing. Following values are valid: chrome firefox htmlUnit internetExplorer opera phantomjs safari
|
iePort
|
|
Default port where to connect for Internet Explorer driver
|
remoteAddress
|
http://localhost:14444/wd/hub
|
Default address for remote driver to connect
|
remoteReusable
|
false
|
The flag which indicates that remote session should be reused between subsequent executions - gives opportunity to reuse browser window for debugging and/or test execution speed-up.
|
reuseCookies
|
false
|
If you are using remote reusable browser, you can force it to reuse cookies
|
chromeDriverBinary
|
|
Path to chromedriver binary
|
ieDriverBinary
|
|
Path to Internet Explorer driver binary
|
firefoxExtensions
|
|
Path or multiple paths to xpi files that will be installed into Firefox instance as extensions. Separate paths using space, use quotes in case that path contains spaces
|
firefox_profile
|
|
Path to Firefox Profile to be used instead of default one delivered with FirefoxDriver
|
firefoxUserPreferences
|
|
Path to Firefox user preferences. This file will be parsed and values will be applied to freshly created Firefox profile.
|
dimensions
|
|
Dimensions of browser window in widthxheight format. This will resize the window if supported by underlying browser. Useful for phantomjs, which by default defines a very small viewport
|
If you need to enable any browser capability, simply specify it as a property in extension configuration. For instance, if you are running Firefox browser and you want to change the binary location, you can do it via following code:
<extension qualifier="webdriver">
<property name="firefox_binary">/path/to/firefox</property>
</extension>
We have enabled JavaScript for htmlUnit driver by default. If you want to disable it, configure appropriate capability to false:
<property name="javascriptEnabled">false</property>
WebDriver expects a Java Object stored in Capabilities settings for some of the WebDriver capabilities. Therefore, we provide a simple mappings to text format for some properties described in table below.
Property name
|
Format
|
loggingPrefs
|
Comma separated list of logging levels for FirefoxDriver. Use driver=${value1},profiler=${value2} where value is one of the following: SEVERE, WARNING, INFO, CONFIG, FINE, FINER or FINEST
|
Graphene 2 configuration
Graphene 2 reuses configuration specified for WebDriver, using webdriver qualifier. You can additionally use a Arquillian Graphene 2 configuration to set Graphene specific configuration, such as default UI timeouts.
Selenium Server configuration
Selenium Server uses selenium-server qualifier.
<extension qualifier="selenium-server">
<property name="host">myhost.org</property>
</extension>
Property name
|
Default value
|
Description
|
avoidProxy
|
false
|
Do not use proxy for connection between clients and server
|
browserSessionReuse
|
false
|
Reuse browser session
|
browserSideLog
|
false
|
Enable logging in browser window
|
debug
|
false
|
Enable debug messages
|
dontTouchLogging
|
false
|
Disable Selenium specific logging configuration
|
ensureCleanSession
|
false
|
Automatic cleanup of the session
|
firefoxProfileTemplate
|
|
Path to the profile used as a template
|
forcedBrowserMode
|
|
Mimic browser mode no matter which one is used to start the client
|
honorSystemProxy
|
false
|
Use system proxy for connections
|
host
|
localhost
|
Name of the machine where to start Selenium Server
|
logFile
|
|
Path to log file
|
nonProxyHosts
|
value of http.nonProxyHosts property
|
List of hosts where proxy settings are ignored
|
port
|
14444
|
Port on machine where to start Selenium Server
|
profilesLocation
|
|
Where profiles are located
|
proxyHost
|
value of http.proxyHost property
|
Name of proxy server
|
proxyInjectionMode
|
false
|
Use proxy approach between Selenium server and client
|
proxyPort
|
value of http.proxyPort property
|
Port of proxy server
|
retryTimeoutInSeconds
|
10
|
Timeout for commands to be retried
|
singleWindow
|
false
|
Use single window
|
skip
|
false
|
Do not manage Selenium Server lifecycle
|
systemProperties
|
|
Arbitrary system properties in -Dproperty.name=property.value format
|
timeoutInSeconds
|
Integer.MAX_VALUE
|
Timeout for Selenium Server
|
trustAllSSLCertificates
|
false
|
Trust all SSL certificates
|
trustStore
|
value of javax.net.ssl.trustStore property
|
Trust store path
|
trustStorePassword
|
value of javax.net.ssl.trustStorePassword property
|
Trust store password
|
userExtensions
|
|
Path to user extension files
|
Selenium Server has different life cycle than Drone instances, it is created and started before test suite and disposed after test suite. Note, you need Selenium Server only if you plan to use remote and reusable instances of WebDriver.
If you have your own Selenium Server instance running, you need either to remove Drone Selenium Server extension from the classpath, set it to a different host/port or disable its execution via skip=true.
Extended configuration, configuring @Qualifier'd Drone instances
If you are wondering how to define configuration for @Qualifier @Drone instance, it's very easy. Only modification you have to do is to change qualifier to include - (@Qualifier annotation name converted to lowercase). For instance, if you qualified Arquillian Graphene instance with @MyExtraBrowser, its extension qualifier will become graphene-myextrabrowser.
Arquillian Drone configures your browser using two-step process:
-
Search for the exact match of qualifier (e.g. graphene-myextrabrowser) in arquillian.xml, if found, step 2 is not performed.
-
Search for a match of base qualifier, without type safe @Qualifier (e.g. graphene) in arquillian.xml.
Then System property are applied in the same fashion.
Arquillian Drone SPI
The big advantage of Arquillian Drone extension is its flexibility. We provide you reasonable defaults, but if they are not sufficient or if they do not fulfill your needs, you can change them. You can change the behavior of existing implementation or implement a support for your own testing framework as well.
Event model
Drone itself is not using Arquillian Container related event, which means that it is able to work with Arquillian Standalone test runners. Arquillian Drone itself observes following events:
Arquillian Event
|
Drone default action
|
BeforeSuite
|
Drone creates a registry with all Drone SPI implementation on the classpath Drone configures Selenium Server Drone registers all Browser Capabilities implementation on the classpath Drone creates a registry for session reuse
|
BeforeClass
|
Drone creates a global configuration Drone creates a configuration for an instance with class scoped life cycle
|
Before
|
Drone creates a configuration for an instance with method scoped life cycle
|
After
|
Drone destroys an instance of method scoped Drone
|
AfterClass
|
Drone destroys an instance of class scoped Drone
|
AfterSuite
|
Drone destroys Selenium Server instance
|
Arquillian Drone Event
|
Drone default action
|
AfterDroneConfigured
|
Drone creates a callable instance of Drone
|
BeforeDroneInstantiated
|
Drone call a service that converts callable instance into real Drone instance
|
AfterDroneInstantiated
|
Drone calls applicable DroneInstanceEnhancer to enhance current Drone instance Drone resizes window if requested
|
AfterDroneEnhanced
|
Drone resizes window if requested
|
BeforeDroneDestroyed
|
Drone calls applicable DroneInstanceEnhancer to deenhance current Drone instance
|
SeleniumServerConfigured
|
Drone starts Selenium Server (internal only)
|
PersistReusedSessionEvent
|
Drone persists reused session (internal only)
|
Arquillian Drone fires following events you can observe in your extension:
Arquillian Drone fired event
|
When is this event fired?
|
AfterDroneConfigured
|
Fired after Drone configuration is created and stored in the context
|
AfterDroneCallableCreated
|
Fired when Drone callable instance is created and stored in the context
|
AfterDroneInstantiated
|
Fired after Drone instance callable is converted into real Drone instance
|
AfterDroneEnhanced
|
Fired after Drone instance is enhanced by an DroneInstanceEnhancer
|
AfterDroneDeenhanced
|
Fired after Drone instance is deenhanced by an DroneInstanceEnhancer
|
AfterDroneDestroyed
|
Fired after Drone instance is destroyed
|
BeforeDroneConfigured
|
Fired before Drone configuration is created
|
BeforeDroneCallableCreated
|
Fired before Drone callable instance is created and stored in the context
|
BeforeDroneInstantiated
|
Fired before Drone instance callable is converted into real Drone instance
|
BeforeDroneEnhanced
|
Fired before Drone instance is enhanced by an DroneInstanceEnhancer
|
BeforeDroneDeenhanced
|
Fired before Drone instance is deenhanced by an DroneInstanceEnhancer
|
BeforeDroneDestroyed
|
Fired before the Drone instance will be destroyed
|
DroneAugmented
|
Fired after WebDriver instance is augmented to support more features.
|
Events provide a class hierarchy, so you can observe their super classes if you want
Working with Drone instances
If you want to support another testing framework and manage it's lifecycle, you should implement following interfaces and register them in your own Arquillian Extension.
Drone Factory SPI:
-
Configurator<T, C>
Provides a way how to configure configurations of type C for @Drone object of type T
-
Instantiator<T, C>
Provides a way how to instantiate @Drone object of type T with configuration C
-
Destructor<T>
Provides a way how to dispose @Drone object of type T
-
DroneInstanceEnhancer<T>
Provides a way how to enhance Drone object of type T with additional functionality. All enhancers available on class path and compatible with current Drone type are always applied.
Drone Context SPI:
-
DroneConfiguration
This is effectively a marker for configuration of type C
-
DroneRegistry
Container for all registered Drone related SPI implementations
-
DroneContext
Holder for Drone instances, callable instances and Drone configurations
-
InstanceOrCallableInstance
Holder for any object in DroneContext. It allows to hold both real instance and callable instance in union like manner. It is also used to hold Drone related configuration, which is always instantiated
Drone WebDriver SPI:
Implementations of Configurator, Instantiator and Destructor are searched on the class path and they are sorted according to precedence they declare. Default implementation has precedence of 0, so if your implementation has a higher precedence and instantiates the exact type, Arquillian Drone will use it instead of default variant. This provides you the ultimate way how to change behavior if desired. Of course, you can provide support for your own framework in the very same way, so in your test you can use @Drone annotation to inject instances of arbitrary web testing framework.